﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;

namespace Xrm.AlmFramework.Console
{
    class Program
    {
        private static List<Tuple<string, string>> switches;
        private static List<string> arguments;

        private static string tfExePath;
        private static string solutionManagerExePath;
        private static string solutionPackagerExePath;

        // SourceControlSolution Params
        private static string solutionName;
        private static string folder, workingFolder;
        private static bool incrementVersion;
        private static string projectFile;
        private static string deployWebResourcesPath;

        // Crm Connection Params
        private static string crmUrl, crmDomain, crmUsername, crmPassword;

        // TFS Connection Params
        private static string tfsCollectionUrl, tfsDomain, tfsUsername, tfsPassword;

        static void Main(string[] args)
        {
            try
            {
                ProcessArguments(args);

                SetUpLogging();

                DisplayBanner();

                foreach (string arg in arguments)
                {
                    switch (arg)
                    {
                        case "?":
                            DisplayUsage();
                            break;
                        case "SourceControlSolution":
                            SourceControlSolution();
                            break;
                        case "BuildDeploySolution":
                            BuildDeploySolution();
                            break;
                    }
                }
            }
            catch (Exception ex)
            {
                DisplayError(ex);
            }
            if (System.Diagnostics.Debugger.IsAttached)
            {
                System.Console.WriteLine("Press any key to continue...");
                System.Console.ReadKey();
            }
        }

        private static void ProcessArguments(string[] args)
        {
            switches = new List<Tuple<string, string>>();
            arguments = new List<string>();

            if (args.Length == 0)
            {
                // by default display usage
                arguments.Add("?");
            }
            else
            {
                // otherwise process args
                foreach (string s in args)
                {
                    if (s.StartsWith(@"\") || s.StartsWith(@"/") || s.StartsWith(@"-"))
                    {
                        if (s.Contains(":"))
                        {
                            var index = s.IndexOf(":");

                            switches.Add(new Tuple<string, string>(s.Substring(1, index - 1).ToLower(), s.Substring(index + 1).Replace("\"", "")));
                        }
                        else
                        {
                            if (s.Substring(1).ToLower() == "?") arguments.Add("?");
                            switches.Add(new Tuple<string, string>(s.Substring(1).ToLower(), string.Empty));
                        }
                    }
                    else
                    {
                        arguments.Add(s);
                    }
                }
            }

            AssignArguments();
        }

        private static void AssignArguments()
        {
            if (switches.FirstOrDefault(c => c.Item1 == "Tf".ToLower()) != null) tfExePath = switches.FirstOrDefault(c => c.Item1 == "Tf".ToLower()).Item2;
            if (switches.FirstOrDefault(c => c.Item1 == "SolutionManager".ToLower()) != null) solutionManagerExePath = switches.FirstOrDefault(c => c.Item1 == "SolutionManager".ToLower()).Item2;
            if (switches.FirstOrDefault(c => c.Item1 == "SolutionPackager".ToLower()) != null) solutionPackagerExePath = switches.FirstOrDefault(c => c.Item1 == "SolutionPackager".ToLower()).Item2;

            if (switches.FirstOrDefault(c => c.Item1 == "CrmUrl".ToLower()) != null) crmUrl = switches.FirstOrDefault(c => c.Item1 == "CrmUrl".ToLower()).Item2;
            if (switches.FirstOrDefault(c => c.Item1 == "CrmDomain".ToLower()) != null) crmDomain = switches.FirstOrDefault(c => c.Item1 == "CrmDomain".ToLower()).Item2;
            if (switches.FirstOrDefault(c => c.Item1 == "CrmUsername".ToLower()) != null) crmUsername = switches.FirstOrDefault(c => c.Item1 == "CrmUsername".ToLower()).Item2;
            if (switches.FirstOrDefault(c => c.Item1 == "crmPassword".ToLower()) != null) crmPassword = switches.FirstOrDefault(c => c.Item1 == "crmPassword".ToLower()).Item2;

            if (switches.FirstOrDefault(c => c.Item1 == "TfsUrl".ToLower()) != null) tfsCollectionUrl = switches.FirstOrDefault(c => c.Item1 == "TfsUrl".ToLower()).Item2;
            if (switches.FirstOrDefault(c => c.Item1 == "TfsDomain".ToLower()) != null) tfsDomain = switches.FirstOrDefault(c => c.Item1 == "TfsDomain".ToLower()).Item2;
            if (switches.FirstOrDefault(c => c.Item1 == "TfsUsername".ToLower()) != null) tfsUsername = switches.FirstOrDefault(c => c.Item1 == "TfsUsername".ToLower()).Item2;
            if (switches.FirstOrDefault(c => c.Item1 == "TfsPassword".ToLower()) != null) tfsPassword = switches.FirstOrDefault(c => c.Item1 == "TfsPassword".ToLower()).Item2;

            if (switches.FirstOrDefault(c => c.Item1 == "SolutionName".ToLower()) != null) solutionName = switches.FirstOrDefault(c => c.Item1 == "SolutionName".ToLower()).Item2;
            if (switches.FirstOrDefault(c => c.Item1 == "ProjectFile".ToLower()) != null) projectFile = switches.FirstOrDefault(c => c.Item1 == "ProjectFile".ToLower()).Item2;
            if (switches.FirstOrDefault(c => c.Item1 == "Folder".ToLower()) != null)
            {
                folder = switches.FirstOrDefault(c => c.Item1 == "Folder".ToLower()).Item2;
                if (folder.Length > 0 && !folder.EndsWith(@"\")) folder += @"\";
            }
            if (switches.FirstOrDefault(c => c.Item1 == "DeployWebResources".ToLower()) != null)
            {
                deployWebResourcesPath = switches.FirstOrDefault(c => c.Item1 == "DeployWebResources".ToLower()).Item2;
            }
            incrementVersion = switches.Any(c => c.Item1 == "IncrementVersion".ToLower());
            workingFolder = folder + @"..\Temp\";

        }

        #region ---------------------------------------- Source Control Solution ------------------------------------

        private static void ValidateSourceControlSolutionArgs()
        {
            // solution validation
            if (string.IsNullOrEmpty(solutionName))
            {
                throw new ArgumentNullException("SolutionName", "Solution Name is a mandatory parameter.");
            }
            // TF exe path validation
            if (string.IsNullOrEmpty(tfExePath))
            {
                throw new ArgumentNullException("TF", "TF exe path is a mandatory parameter.");
            }
            if (!tfExePath.ToLower().EndsWith("tf.exe")) tfExePath += "TF.exe";
            if (!System.IO.File.Exists(tfExePath)) throw new ArgumentException("TF executable path is invalid - " + tfExePath);

            // Solution Manager exe path validation
            if (string.IsNullOrEmpty(solutionManagerExePath))
            {
                throw new ArgumentNullException("SolutionManager", "Solution Manager exe path is a mandatory parameter.");
            }
            if (!solutionManagerExePath.ToLower().EndsWith("solutionmanager.exe")) solutionManagerExePath += "SolutionManager.exe";
            if (!System.IO.File.Exists(solutionManagerExePath)) throw new ArgumentException("Solution Manager executable path is invalid - " + solutionManagerExePath);

            // Solution Packager exe path validation
            if (string.IsNullOrEmpty(solutionPackagerExePath))
            {
                throw new ArgumentNullException("SolutionPackager", "Solution Packager exe path is a mandatory parameter.");
            }
            if (!solutionPackagerExePath.ToLower().EndsWith("solutionpackager.exe")) solutionPackagerExePath += "SolutionPackager.exe";
            if (!System.IO.File.Exists(solutionPackagerExePath)) throw new ArgumentException("Solution Packager executable path is invalid - " + solutionPackagerExePath);

            // Folder
            if (string.IsNullOrEmpty(folder))
            {
                throw new ArgumentNullException("Folder", "Folder is a mandatory parameter.");
            }
        }

        private static void SourceControlSolution()
        {
            Trace.WriteLine("Starting SourceControlSolution...." + DateTime.Now.ToString("dd / MMM / yyyy HH:mm:ss"));

            ValidateSourceControlSolutionArgs();

            /*
            0. Gets the latest web resources from TFS, uploads them to the target server and publishes
            1. Exports the CRM solution from the target server
            2. Checks out a workspace folder in TFS
            3. Unpacks the solution to the workspace folder
            4. Updates the Visual Studio project with the solution files
            5. Checks in the workspace 
            */

            if (deployWebResourcesPath != null) DeployWebResources();

            CreateWorkingFolder();

            string exportSolutionArgs = string.Format(@"Export /OutputPath:""{0}"" /SolutionName:{1} /PublishAll /Timeout:1200 /Url:""{2}"" /Domain:""{3}"" /Username:""{4}"" /Password:""{5}"" /NoPrompt /NoLogo",
                                                    workingFolder.Substring(0, workingFolder.Length - 1), solutionName,
                                                    crmUrl, crmDomain, crmUsername, crmPassword);
            if (incrementVersion) exportSolutionArgs += " /IncrementVersion";

            Trace.WriteLine("Exporting solution:" + solutionManagerExePath + " " + exportSolutionArgs);

            // 1. Exports the CRM solution from the target server
            ExecuteSubProcess(solutionManagerExePath, "SolutionManager.exe", exportSolutionArgs);

            string solutionZipFile = GetExportedFileName();

            // 2. Check out workspace folder
            string tfGetArgs = string.Format(@" get ""{0}..\"" /recursive /login:""{1}\{2}"",""{3}"" /noprompt", folder, tfsDomain, tfsUsername, tfsPassword);
            Trace.WriteLine("TFS Get Latest: " + tfExePath + " " + tfGetArgs);
            ExecuteSubProcess(tfExePath, "tf.exe", tfGetArgs);

            string tfsCheckoutArgs = string.Format(@" checkout ""{0}..\"" /recursive /login:""{1}\{2}"",""{3}"" /noprompt", folder, tfsDomain, tfsUsername, tfsPassword);
            Trace.WriteLine("TFS Checkout: " + tfExePath + " " + tfsCheckoutArgs);
            ExecuteSubProcess(tfExePath, "tf.exe", tfsCheckoutArgs);

            // 3. Unpacks the solution to the workspace folder
            string solUnpackArgs = string.Format(@" /a:Extract /zipfile:""{0}"" /folder:""{1}"" /allowDelete:No /clobber /nologo", solutionZipFile, folder.Substring(0, folder.Length - 1));
            Trace.WriteLine("Unpacking solution file: {0}", solutionZipFile);
            ExecuteSubProcess(solutionPackagerExePath, "SolutionPackager.exe", solUnpackArgs);

            List<CrmSolutionAnalyser.Target> deletions = GetFilesForDeletion();
            Trace.WriteLine(string.Format("{0} items identified for deletion.", deletions.Count.ToString()));

            //4. Updates the Visual Studio project with the solution files
            VisualStudioProject vsproject = new VisualStudioProject(projectFile);
            vsproject.AddFiles(folder);
            vsproject.Remove(deletions);
            vsproject.Save();

            // Add new files to TFS
            string tfsAddArgs = string.Format(@" add ""{0}"" /recursive /login:""{1}\{2}"",""{3}"" /noprompt", folder, tfsDomain, tfsUsername, tfsPassword);
            Trace.WriteLine("TFS Add: " + tfExePath + " " + tfsAddArgs);
            ExecuteSubProcess(tfExePath, "tf.exe", tfsAddArgs);

            // Delete files from TFS
            foreach (CrmSolutionAnalyser.Target target in deletions)
            {
                string tfsUndoArgs = string.Format(@" undo ""{0}"" /recursive /login:""{1}\{2}"",""{3}"" /noprompt", target.Path, tfsDomain, tfsUsername, tfsPassword);
                Trace.WriteLine("TFS Undo: " + tfExePath + " " + tfsUndoArgs);
                ExecuteSubProcess(tfExePath, "tf.exe", tfsUndoArgs);

                string tfsDeleteArgs = string.Format(@" delete ""{0}"" /recursive /login:""{1}\{2}"",""{3}"" /noprompt", target.Path, tfsDomain, tfsUsername, tfsPassword);
                Trace.WriteLine("TFS Delete: " + tfExePath + " " + tfsDeleteArgs);
                ExecuteSubProcess(tfExePath, "tf.exe", tfsDeleteArgs);
            }

            //5. Checks in the workspace
            string tfsCheckinArgs = string.Format(@" checkin ""{0}..\"" /recursive /login:""{1}\{2}"",""{3}"" /override:""Automated Build - Source Control Solution"" /noprompt", folder, tfsDomain, tfsUsername, tfsPassword);
            Trace.WriteLine("TFS Checkin: " + tfExePath + " " + tfsCheckinArgs);
            ExecuteSubProcess(tfExePath, "tf.exe", tfsCheckinArgs);

            // Clean up...
            System.IO.Directory.Delete(workingFolder, true);

            Trace.WriteLine("Completed SourceControlSolution - " + DateTime.Now.ToString("dd / MMM / yyyy HH:mm:ss"));
        }

        private static List<CrmSolutionAnalyser.Target> GetFilesForDeletion()
        {
            // 1. Compare disk files with those listed in root components
            string solutionFile = folder + @"Other\Solution.xml";

            if (System.IO.File.Exists(solutionFile))
            {
                CrmSolutionAnalyser soln = new CrmSolutionAnalyser(solutionFile);
                return soln.IdentifyDeletions(folder);
            }
            else
            {
                return null;
            }
        }

        private static void CreateWorkingFolder()
        {
            if (System.IO.Directory.Exists(workingFolder))
            {
                System.IO.Directory.Delete(workingFolder, true);
            }
            System.IO.Directory.CreateDirectory(workingFolder);
        }

        private static string GetExportedFileName()
        {
            string retVal = "";
            foreach (string f in System.IO.Directory.GetFiles(workingFolder))
            {
                retVal = f;
                break;
            }
            return retVal;
        }

        #endregion

        #region ---------------------------------------- Build & Deploy Solution ------------------------------------

        private static void ValidateBuildDeploySolutionArgs()
        {
            // solution validation
            if (string.IsNullOrEmpty(solutionName))
            {
                throw new ArgumentNullException("SolutionName", "Solution Name is a mandatory parameter.");
            }

            // Solution Manager exe path validation
            if (string.IsNullOrEmpty(solutionManagerExePath))
            {
                throw new ArgumentNullException("SolutionManager", "Solution Manager exe path is a mandatory parameter.");
            }
            if (!solutionManagerExePath.ToLower().EndsWith("solutionmanager.exe")) solutionManagerExePath += "SolutionManager.exe";
            if (!System.IO.File.Exists(solutionManagerExePath)) throw new ArgumentException("Solution Manager executable path is invalid - " + solutionManagerExePath);

            // Solution Packager exe path validation
            if (string.IsNullOrEmpty(solutionPackagerExePath))
            {
                throw new ArgumentNullException("SolutionPackager", "Solution Packager exe path is a mandatory parameter.");
            }
            if (!solutionPackagerExePath.ToLower().EndsWith("solutionpackager.exe")) solutionPackagerExePath += "SolutionPackager.exe";
            if (!System.IO.File.Exists(solutionPackagerExePath)) throw new ArgumentException("Solution Packager executable path is invalid - " + solutionPackagerExePath);

            // Folder
            if (string.IsNullOrEmpty(folder))
            {
                throw new ArgumentNullException("Folder", "Folder is a mandatory parameter.");
            }

            // Crm Argument validation
            if (string.IsNullOrEmpty(crmUrl))
            {
                throw new ArgumentNullException("CrmUrl", "CrmUrl is a mandatory parameter.");
            }
        }

        private static void BuildDeploySolution()
        {
            /*
             * 1. Package file to a zipped solution.
             * 2. Import zip file to CRM server.
             * 3. Publish all customisations.
             * 
             */

            Trace.WriteLine("Starting BuildDeploySolution...." + DateTime.Now.ToString("dd / MMM / yyyy HH:mm:ss"));

            if (Environment.MachineName.ToLower() == "vmw004" || Environment.MachineName.ToLower() == "vmwhotfix")
            {
                throw new ArgumentOutOfRangeException("Server", "Build Deploy Solution is not valid on the master development servers.");
            }

            ValidateBuildDeploySolutionArgs();

            CreateWorkingFolder();

            // 1. Packs the solution to the temp folder
            string zipFile = workingFolder + solutionName + ".zip";
            string solPackArgs = string.Format(@" /a:Pack /zipfile:""{0}"" /folder:""{1}"" /packagetype:Unmanaged /allowDelete:No /clobber /nologo", zipFile, folder.Substring(0, folder.Length - 1));
            Trace.WriteLine(string.Format("Packaging solution file: {0}", zipFile));
            Trace.WriteLine(solutionPackagerExePath + " " + solPackArgs);
            ExecuteSubProcess(solutionPackagerExePath, "SolutionPackager.exe", solPackArgs);

            if (System.IO.File.Exists(zipFile))
            {
                string importSolutionArgs = string.Format(@"Import /Path:""{0}"" /SolutionName:{1} /ActivateSDKSteps /ActivateWorkflows /PublishAll /Timeout:1200 /Url:""{2}"" /NoPrompt /NoLogo",
                                                    zipFile, solutionName, crmUrl);
                if (!string.IsNullOrEmpty(crmUsername))
                {
                    importSolutionArgs += string.Format(" /Domain:{0} /Username:{1} /Password:{2}", crmDomain, crmUsername, crmPassword);
                }

                Trace.WriteLine("Importing solution:" + solutionManagerExePath + " " + importSolutionArgs);

                // 1. Exports the CRM solution from the target server
                ExecuteSubProcess(solutionManagerExePath, "SolutionManager.exe", importSolutionArgs);

            }

            // Clean up...
            System.IO.Directory.Delete(workingFolder, true);

            Trace.WriteLine("Completed BuildDeploySolution - " + DateTime.Now.ToString("dd / MMM / yyyy HH:mm:ss"));
        }

        #endregion

        #region ---------------------------------------- Deploy Web Resources ---------------------------------------

        private static void ValidateDeployWebResourcesArgs()
        {
            // solution validation
            if (string.IsNullOrEmpty(solutionName))
            {
                throw new ArgumentNullException("SolutionName", "Solution Name is a mandatory parameter.");
            }

            // Solution Manager exe path validation
            if (string.IsNullOrEmpty(solutionManagerExePath))
            {
                throw new ArgumentNullException("SolutionManager", "Solution Manager exe path is a mandatory parameter.");
            }
            if (!solutionManagerExePath.ToLower().EndsWith("solutionmanager.exe")) solutionManagerExePath += "SolutionManager.exe";
            if (!System.IO.File.Exists(solutionManagerExePath)) throw new ArgumentException("Solution Manager executable path is invalid - " + solutionManagerExePath);

            // Folder
            if (string.IsNullOrEmpty(folder))
            {
                throw new ArgumentNullException("Folder", "Folder is a mandatory parameter.");
            }

            // Crm Argument validation
            if (string.IsNullOrEmpty(crmUrl))
            {
                throw new ArgumentNullException("CrmUrl", "CrmUrl is a mandatory parameter.");
            }

            if (string.IsNullOrEmpty(deployWebResourcesPath))
            {
                throw new ArgumentNullException("DeployWebResources", "DeployWebResources is a mandatory parameter.");
            }

        }

        private static void DeployWebResources()
        {
            Trace.WriteLine("Starting DeployWebResources...." + DateTime.Now.ToString("dd / MMM / yyyy HH:mm:ss"));

            // validate args...
            ValidateDeployWebResourcesArgs();

            // get latest web resources from source control
            string tfGetArgs = string.Format(@" get ""{0}"" /recursive /login:""{1}\{2}"",""{3}"" /noprompt", deployWebResourcesPath, tfsDomain, tfsUsername, tfsPassword);
            Trace.WriteLine("TFS Get Latest: " + tfExePath + " " + tfGetArgs);
            ExecuteSubProcess(tfExePath, "tf.exe", tfGetArgs);

            // Upload web resources into target CRM server and publish
            string deployWebResourceArgs = string.Format(@"ImportWebResources /Directory:""{0}""  /SolutionName:""{1}"" /PublishAll /url:""{2}"" /Timeout:1200  /Nologo /Noprompt",
                                                    deployWebResourcesPath, solutionName, crmUrl);
            if (!string.IsNullOrEmpty(crmUsername))
            {
                deployWebResourceArgs += string.Format(" /Domain:{0} /Username:{1} /Password:{2}", crmDomain, crmUsername, crmPassword);
            }

            Trace.WriteLine("Deploying web resources:" + solutionManagerExePath + " " + deployWebResourceArgs);

            ExecuteSubProcess(solutionManagerExePath, "SolutionManager.exe", deployWebResourceArgs);

            Trace.WriteLine("Completed DeployWebResources - " + DateTime.Now.ToString("dd / MMM / yyyy HH:mm:ss"));
        }

        #endregion

        #region ---------------------------------------- Common Methods ---------------------------------------------

        private static void ExecuteSubProcess(string exePath, string exeName, string args)
        {
            var baseDir = Environment.CurrentDirectory;
            Environment.CurrentDirectory = exePath.ToLower().Replace(exeName.ToLower(), "");

            Process p = new Process();
            p.StartInfo.FileName = exePath;
            p.StartInfo.Arguments = args;
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = true;
            p.OutputDataReceived += new DataReceivedEventHandler(DisplaySubProcessOutput);
            p.Start();
            p.BeginOutputReadLine();
            p.WaitForExit();

            p.Close();

            Environment.CurrentDirectory = baseDir;
        }

        private static void DisplaySubProcessOutput(object sender, DataReceivedEventArgs e)
        {
            Trace.WriteLine(e.Data);
        }

        private static void DisplayBanner()
        {
            if (switches.Any(c => c.Item1 == "Nologo".ToLower()) == false)
            {
                Trace.WriteLine("-------------------------------------------------------------------------------");
                Trace.WriteLine("XRM APPLICATION LIFECYCLE MANAGEMENT :: Dynamics CRM 2011 :: " + DateTime.Now.ToString("dd / MMM / yyyy HH:mm:ss"));
                Trace.WriteLine("-------------------------------------------------------------------------------");
            }
        }

        private static void DisplayUsage()
        {
            Trace.Write(System.IO.File.ReadAllText("ReadMe.txt"));
        }

        private static void DisplayError(System.Exception ex)
        {
            Trace.WriteLine("");
            Trace.WriteLine("-- ERROR --");
            Trace.WriteLine("-------------------------------------------------------------------------------");
            Trace.WriteLine(ex.Message);
            Trace.WriteLine(ex.StackTrace);
            Trace.WriteLine("-------------------------------------------------------------------------------");
        }

        private static void SetUpLogging()
        {
            Trace.Listeners.Clear();
            // log to console
            ConsoleTraceListener ctl = new ConsoleTraceListener(false);
            ctl.TraceOutputOptions = TraceOptions.DateTime;
            Trace.Listeners.Add(ctl);

            // log to text file
            if (switches.Any(c => c.Item1 == "Log".ToLower()))
            {
                string logFile = switches.FirstOrDefault(c => c.Item1 == "Log".ToLower()).Item2;
                logFile = logFile.Replace("{yyyyMMdd}", DateTime.Now.ToString("yyyyMMdd"));
                TextWriterTraceListener twtl = new TextWriterTraceListener(logFile);
                twtl.Name = "TextLogger";
                twtl.TraceOutputOptions = TraceOptions.ThreadId | TraceOptions.DateTime;
                Trace.Listeners.Add(twtl);
            }

            Trace.AutoFlush = true;
        }

        #endregion
    }
}
